package com.quectel.core.module.car.service.impl.mongo;


import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.func.LambdaUtil;
import com.quectel.constant.core.ResourceGroupCodeConstants;
import com.quectel.constant.global.StatisticsConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.core.constants.MongoCollectionConstants;
import com.quectel.core.module.MongoAuthComponent;
import com.quectel.core.module.ai.entity.mongo.MongoAiEventRecordModel;
import com.quectel.core.module.car.dto.mongo.MongoCarAccessRecordDto;
import com.quectel.core.module.car.entity.mongo.MongoCarAccessRecordModel;
import com.quectel.core.module.car.service.mongo.MongoCarAccessRecordService;
import com.quectel.core.module.household.dto.datav.PersonInOutTrendDto;
import com.quectel.core.module.village.dto.VillageDto;
import com.quectel.core.module.village.service.VillageService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.Request;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mongo.MongodbUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author caolu
 * @email louis.cao@quectel.com
 * @date 2021-11-17 16:01:41
 */
@DubboService
public class MongoCarAccessRecordServiceImpl implements MongoCarAccessRecordService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MongoCarAccessRecordServiceImpl.class);


    @Autowired
    private MongoAuthComponent mongoAuthComponent;
    @Autowired
    private VillageService villageService;

    private Criteria sqlWhere(Map<String, Object> params) {
        Request request = Request.configParams(params);

        Criteria authByAllQuery = mongoAuthComponent.buildAuthByAllQuery(params
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getTenantId)
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getDeviceCategoryId)
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getDeviceTypeId)
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getDeviceId)
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getVillageId)
                , LambdaUtil.getFieldName(MongoAiEventRecordModel::getBuildingId)
        );
        Criteria filterResourceGroupCode = new Criteria();
        if (StringUtils.isNotBlank(request.getString(ResourceGroupCodeConstants.AUTH_RESOURCE_GROUP_CODE_FLAG))) {
            filterResourceGroupCode.andOperator(mongoAuthComponent.buildFilterByResourceGroupQuery(params
                    , LambdaUtil.getFieldName(MongoAiEventRecordModel::getDeviceCategoryId)
                    , LambdaUtil.getFieldName(MongoAiEventRecordModel::getDeviceTypeId)
            ));
        }


        Criteria otherFieldQuery = new Criteria();

        Date startTime = request.getDate(SystemConstants.TIME_SLOT_KEY_PAIR.getLeft(), DateUtils.FormatType.COMMON.getType());
        Date endTime = request.getDate(SystemConstants.TIME_SLOT_KEY_PAIR.getRight(), DateUtils.FormatType.COMMON.getType());
        if (startTime != null && endTime != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getAccessTime)).gte(startTime).lte(endTime);
        }

        Long deviceId = request.getLong(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getDeviceId));
        if (deviceId != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getDeviceId)).is(deviceId);
        }

        Long villageId = request.getLong(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getVillageId));
        if (villageId != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getVillageId)).is(villageId);
        }

        String deviceName = request.getString(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getDeviceName));
        if (StringUtils.isNotBlank(deviceName)) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getDeviceName)).regex(".*?\\" + deviceName + ".*");
        }

        Byte outOrIn = request.getByte(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getOutOrIn));
        if (outOrIn != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getOutOrIn)).is(outOrIn);
        }

        Long carId = request.getLong(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarId));
        if (carId != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarId)).is(carId);
        }

        String license = request.getString(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getLicense));
        if (StringUtils.isNotBlank(license)) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getLicense)).is(license);
        }

        Byte carType = request.getByte(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarType));
        if (carType != null) {
            otherFieldQuery.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarType)).is(carType);
        }
        return new Criteria().andOperator(authByAllQuery, filterResourceGroupCode, otherFieldQuery);
    }

    @Override
    public void saveBatch(List<MongoCarAccessRecordDto> list) {

        MongodbUtils.insertMany(CopyUtils.copyList(list, MongoCarAccessRecordModel.class), MongoCollectionConstants.CAR_ACCESS_RECORD);
    }

    @Override
    public int countTotal(Map<String, Object> params) {

        Request request = Request.configParams(params);
        Criteria query = new Criteria();
        Date startTime = request.getDate(SystemConstants.TIME_SLOT_KEY_PAIR.getLeft(), DateUtils.FormatType.COMMON.getType());
        Date endTime = request.getDate(SystemConstants.TIME_SLOT_KEY_PAIR.getRight(), DateUtils.FormatType.COMMON.getType());

        if (startTime != null && endTime != null) {
            query.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getAccessTime))
                    .gte(startTime).lte(endTime);
        }
        Long villageId = request.getLong(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getVillageId));
        if (villageId != null) {
            query.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getVillageId)).is(villageId);
        }

        Byte outOrIn = request.getByte(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getOutOrIn));
        if (outOrIn != null) {
            query.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getOutOrIn)).is(outOrIn);
        }

        Byte carType = request.getByte(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarType));
        if (carType != null) {
            query.and(LambdaUtil.getFieldName(MongoCarAccessRecordModel::getCarType)).is(carType);
        }

        return (int) MongodbUtils.count(new Query(query), MongoCollectionConstants.CAR_ACCESS_RECORD);
    }

    /**
     * 社区home首页查询
     * @param params
     * @return
     */
    @Override
    public List<PersonInOutTrendDto> groupTimeType(Map<String, Object> params) {
        List<PersonInOutTrendDto> result = new ArrayList<>();

        if ("DAY".equals(StatisticsConstants.TIME_RANGE_BY_DAY)) {
            AggregationResults<Document> aggregate = MongodbUtils.aggregate(
                    Aggregation.newAggregation(
                            Aggregation.match(sqlWhere(params)),
                            Aggregation.project().andExpression(" {$dateToString:{\"format\":\"%Y-%m-%d\",\"date\":\"$accessTime\",\"timezone\":\"+08:00\"}}").as("dt"),
                            Aggregation.group("dt").count().as("cnt")
                    ), MongoCollectionConstants.CAR_ACCESS_RECORD, Document.class);

            Iterator<Document> iterator = aggregate.iterator();
            while (iterator.hasNext()) {
                Document next = iterator.next();
                PersonInOutTrendDto personInOutTrendDto = new PersonInOutTrendDto();
                personInOutTrendDto.setTime(Convert.convert(String.class, next.get("_id")));
                personInOutTrendDto.setCount(Convert.convert(Integer.class, next.get("cnt")));
                result.add(personInOutTrendDto);
            }
        }

        if ("MONTH".equals(StatisticsConstants.TIME_RANGE_BY_MONTH)){
            AggregationResults<Document> aggregate = MongodbUtils.aggregate(
                    Aggregation.newAggregation(
                            Aggregation.match(sqlWhere(params)),
                            Aggregation.project().andExpression(" {$dateToString:{\"format\":\"%Y-%m\",\"date\":\"$accessTime\",\"timezone\":\"+08:00\"}}").as("dt"),
                            Aggregation.group("dt").count().as("cnt")
                    ), MongoCollectionConstants.CAR_ACCESS_RECORD, Document.class);

            Iterator<Document> iterator = aggregate.iterator();
            while (iterator.hasNext()) {
                Document next = iterator.next();
                PersonInOutTrendDto personInOutTrendDto = new PersonInOutTrendDto();
                personInOutTrendDto.setTime(Convert.convert(String.class, next.get("_id")));
                personInOutTrendDto.setCount(Convert.convert(Integer.class, next.get("cnt")));
                result.add(personInOutTrendDto);
            }
        }

        return result;
    }

    @Override
    public List<PersonInOutTrendDto> groupHours(Map<String, Object> params) {
        List<PersonInOutTrendDto> result = new ArrayList<>();

        AggregationResults<Document> aggregate = MongodbUtils.aggregate(
                Aggregation.newAggregation(
                        Aggregation.match(sqlWhere(params)),
                        Aggregation.project().andExpression(" {$dateToString:{\"format\":\"%H\",\"date\":\"$accessTime\",\"timezone\":\"+08:00\"}}").as("dt"),
                        Aggregation.group("dt").count().as("cnt")
                ), MongoCollectionConstants.CAR_ACCESS_RECORD, Document.class);

        Iterator<Document> iterator = aggregate.iterator();
        while (iterator.hasNext()) {
            Document next = iterator.next();
            PersonInOutTrendDto personInOutTrendDto = new PersonInOutTrendDto();
            personInOutTrendDto.setTime(Convert.convert(String.class, next.get("_id")));
            personInOutTrendDto.setCount(Convert.convert(Integer.class, next.get("cnt")));
            result.add(personInOutTrendDto);
        }
        return result;
    }

    @Override
    public List<PersonInOutTrendDto> groupDays(Map<String, Object> params) {

        List<PersonInOutTrendDto> result = new ArrayList<>();

        AggregationResults<Document> aggregate = MongodbUtils.aggregate(
                Aggregation.newAggregation(
                        Aggregation.match(sqlWhere(params)),
                        Aggregation.project().andExpression(" {$dateToString:{\"format\":\"%m-%d\",\"date\":\"$accessTime\",\"timezone\":\"+08:00\"}}").as("dt"),
                        Aggregation.group("dt").count().as("cnt")
                ), MongoCollectionConstants.CAR_ACCESS_RECORD, Document.class);

        Iterator<Document> iterator = aggregate.iterator();
        while (iterator.hasNext()) {
            Document next = iterator.next();
            PersonInOutTrendDto personInOutTrendDto = new PersonInOutTrendDto();
            personInOutTrendDto.setTime(Convert.convert(String.class, next.get("_id")));
            personInOutTrendDto.setCount(Convert.convert(Integer.class, next.get("cnt")));
            result.add(personInOutTrendDto);
        }
        return result;
    }

    @Override
    public MongoCarAccessRecordDto selectById(Long id) {
        MongoCarAccessRecordModel one = MongodbUtils.findOne(id, MongoCollectionConstants.CAR_ACCESS_RECORD, MongoCarAccessRecordModel.class);
        return CopyUtils.copyObj(one, MongoCarAccessRecordDto.class);
    }

    @Override
    public List<MongoCarAccessRecordDto> queryList(Map<String, Object> params) {
        Request request = Request.configParams(params);
        return
                MongodbUtils.page(
                        new Query(sqlWhere(params)),
                        request.getPageNo() - 1,
                        request.getPageSize(),
                        Sort.by(Sort.Direction.DESC, SystemConstants.MONGO_ENTITY_ID_FLAG),
                        MongoCarAccessRecordModel.class,
                        MongoCollectionConstants.CAR_ACCESS_RECORD
                ).stream().map(model -> {
                    MongoCarAccessRecordDto dto = CopyUtils.copyObj(model, MongoCarAccessRecordDto.class);

                    VillageDto villageDto = villageService.selectCacheById(dto.getVillageId());
                    dto.setVillageDto(villageDto);
                    return dto;
                }).collect(Collectors.toList());
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return (int) MongodbUtils.count(new Query(sqlWhere(params)), MongoCollectionConstants.CAR_ACCESS_RECORD);
    }

    @Override
    public Long save(MongoCarAccessRecordDto dto) {
        MongoCarAccessRecordModel entity = CopyUtils.copyObj(dto, MongoCarAccessRecordModel.class);
        entity.setId(Snowflake.nextId());
        MongodbUtils.insert(entity, MongoCollectionConstants.CAR_ACCESS_RECORD);
        return entity.getId();
    }

    @Override
    public void deleteById(Long id) {
        MongodbUtils.delete(id, MongoCollectionConstants.CAR_ACCESS_RECORD);
    }

    @Override
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            MongodbUtils.delete(id, MongoCollectionConstants.CAR_ACCESS_RECORD);
        }
    }
}
